#include "mesh_model_presenters.h"
#include <QSignalMapper>
#include <QMessageBox>
#include <QFileDialog>
#include <QtDebug>

#include <framework/component_factory.h>
#include <framework/mvp.h>
#include <widgets/main_frame.h>
#include <widgets/mesh_widget.h>
#include <util/gref.h>

#include "ui_screenshot.h"

widgets::MainFrame * g_pMainFrame;
extern widgets::MainFrame * g_pMainFrame;

namespace presenters { namespace mesh_model {

  void CalculateStress(models::Mesh *pModel, int elementIndex)
  {
    if (elementIndex < 0)
      return;

    using namespace models;

    models::Mesh *pMesh0 = pModel;
    const QObjectList & childs = pModel->children();

    foreach (QObject * pObject, childs)
    {
      models::Mesh *pMesh1 = qobject_cast<models::Mesh *>(pObject);

      if (!pMesh1)
        continue;

      qreal SigmaIn[3][3] = {};
      {
        QVector<models::Mesh::Scalar_field::SP> Fields = pMesh0->get_frame()->get_element_field(IMesh::element_triangle);
        foreach (models::Mesh::Scalar_field::SP pField, Fields)
        {
          const qreal val = pField->get_values()[elementIndex * 3];

          if (pField->get_name() == "XXStressParameterName")
            SigmaIn[0][0] = val;
          if (pField->get_name() == "YYStressParameterName")
            SigmaIn[1][1] = val;
          if (pField->get_name() == "ZZStressParameterName")
            SigmaIn[2][2] = val;
          if (pField->get_name() == "XYStressParameterName")
            SigmaIn[0][1] = SigmaIn[1][0] = val;
          if (pField->get_name() == "XZStressParameterName")
            SigmaIn[0][2] = SigmaIn[2][0] = val;
          if (pField->get_name() == "YZStressParameterName")
            SigmaIn[1][2] = SigmaIn[2][1] = val;
        }
      }
      
      QVector<models::Mesh::Scalar_field::SP> Fields = pMesh1->get_frame()->get_element_field(IMesh::element_triangle);
      {
        const size_t size = Fields[0]->get_values().size();
        QVector<qreal> s11(size);
        QVector<qreal> s22(size);
        QVector<qreal> s33(size);
        QVector<qreal> s12(size);
        QVector<qreal> s13(size);
        QVector<qreal> s23(size);
        
        foreach (models::Mesh::Scalar_field::SP pField, Fields)
        {
          const QString FieldName = pField->get_name().toUpper();
          const QString Prefix    = FieldName.left(3);

          if (Prefix != "SCT" &&
              Prefix != "STC")
            continue;

          const QVector<qreal> & sct = pField->get_values();

          QVector<qreal> * pSigmaOut = 0;

          const QString IndOut = FieldName.mid(4, 2);

          if (IndOut == "11")
            pSigmaOut = &s11;

          if (IndOut == "22")
            pSigmaOut = &s22;

          if (IndOut == "33")
            pSigmaOut = &s33;

          if (IndOut == "12")
            pSigmaOut = &s12;
          
          if (IndOut == "13")
            pSigmaOut = &s13;

          if (IndOut == "23")
            pSigmaOut = &s23;

          const int IndIn1 = FieldName[6].toAscii() - '1';
          const int IndIn2 = FieldName[7].toAscii() - '1';

          for (int i = 0; i < size; ++i)
            (*pSigmaOut)[i] += SigmaIn[IndIn1][IndIn2] * sct[i];
        }

        foreach (models::Mesh::Scalar_field::SP pField, Fields)
        {
          if (pField->get_name() == "XXStressParameterName")
            pField->get_values() = s11;
          else
          if (pField->get_name() == "YYStressParameterName")
            pField->get_values() = s22;
          else
          if (pField->get_name() == "ZZStressParameterName")
            pField->get_values() = s33;
          else
          if (pField->get_name() == "XYStressParameterName")
            pField->get_values() = s12;
          else
          if (pField->get_name() == "XZStressParameterName")
            pField->get_values() = s13;
          else
          if (pField->get_name() == "YZStressParameterName")
            pField->get_values() = s23;
          else
            continue;

          pField->recalc();
        }
        pMesh1->set_current_frame(0);
      }
    }
  }

  //
  // presenters::models::Mesh::Mesh
  //

  Mesh::Mesh(models::Mesh *p_model, widgets::Mesh *p_view)
  : QObject(0)
  , mWrapper(p_model, p_view->parentWidget()->windowTitle())
  {
    p_model = mWrapper.GetSubModel();

    p_view->AddMesh("", &mWrapper);
    p_view->RecalculateBounds();
    connect(p_model, SIGNAL(changed()), this, SLOT(on_update_model()));
    on_update_model();

    p_view->setContextMenuPolicy(Qt::CustomContextMenu);
    connect(p_view, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(OnContextMenuRequested(const QPoint &)));
    connect(p_view, SIGNAL(OnElementPicked(IMesh::element_type, int)), this, SLOT(OnElementPicked(IMesh::element_type, int)));
    connect(p_view, SIGNAL(OnElementPickedDouble(IMesh::element_type, int)), this, SLOT(OnElementPickedDouble(IMesh::element_type, int)));
  }

  Mesh::~Mesh()
  {
  }

  void Mesh::on_update_model()
  {
    using namespace models;

    models::Mesh * pModel = mWrapper.GetSubModel();

    models::Mesh::Scalar_field::SP pField = pModel->get_frame()->get_param(mWrapper.mFieldName);

    if (!pField)
      pField = pModel->get_frame()->get_element_param(IMesh::element_triangle, mWrapper.mFieldName);

    widgets::Mesh * pView = framework::GetPresenterView< widgets::Mesh * >(this);
    if (pField)
      pView->SetStops(pModel->get_gradient_stops(), pField->get_min(), pField->get_max(), mWrapper.mFieldName);
    else
      pView->SetStops(QGradientStops(), 0.0, 0.0, "");

    pView->updateGL();
  }
  
  void Mesh::OnContextMenuRequested(const QPoint & _Pos)
  {
    using namespace models;
    widgets::Mesh * pView = framework::GetPresenterView< widgets::Mesh * >(this);
    QMenu Menu(pView);

    models::Mesh::Frame::SP pFrame = mWrapper.GetSubModel()->get_frame();

    QObject::connect(Menu.addAction(tr("Wireframe")), SIGNAL(triggered()), this, SLOT(OnFieldAction()));

    if (pFrame->get_param_qty() > 0)
    { // veretex fields
      QMenu *pVertexMenu = Menu.addMenu(tr("Vertex fields"));

      QStringList FieldNames;
      for (int i = 0; i < pFrame->get_param_qty(); i++)
        FieldNames.push_back(pFrame->get_param(i)->get_name());
      FieldNames.sort();

      foreach(const QString & FieldName, FieldNames)
      {
        QAction * pAction = pVertexMenu->addAction(FieldName, this, SLOT(on_update_model()));
        QObject::connect(pAction, SIGNAL(triggered()), this, SLOT(OnFieldAction()));
      }
    }

    { // element fields
      QMenu *pElementMenu = Menu.addMenu(tr("Element fields"));

      for (int type = 0; type < IMesh::element_qty; type++)
      {
        IMesh::element_type element_type = IMesh::element_type(type);
        
        const int param_qty = pFrame->get_element_param_qty(element_type);
        
        QStringList FieldNames;
        for (int i = 0; i < param_qty; i++)
          FieldNames.push_back(pFrame->get_element_param(element_type, i)->get_name());
        FieldNames.sort();

        foreach(const QString & FieldName, FieldNames)
        {
          QAction * pAction = pElementMenu->addAction(FieldName, this, SLOT(on_update_model()));
          QObject::connect(pAction, SIGNAL(triggered()), this, SLOT(OnFieldAction()));
        }
      }
    }

    Menu.addSeparator();
    QMultiMap<int, QObject *> ModelLevelMap;

    UpdateModelLevelMap(ModelLevelMap, 0, mWrapper.GetRootModel());

    QMenu *pMenuModel = Menu.addMenu(tr("Model"));
    int Level = 0;
    while (true)
    {
      QList<QObject *> ModelList = ModelLevelMap.values(Level);

      if (ModelList.isEmpty())
        break;

      foreach(QObject * pModel, ModelList)
        connect(pMenuModel->addAction(pModel->objectName()), SIGNAL(triggered()), this, SLOT(OnModelAction()));

      pMenuModel->addSeparator();

      Level++;
    }

    QMenu *pMenuComponents = Menu.addMenu(tr("Components"));

    connect(Menu.addAction(tr("Save screenshot...")), SIGNAL(triggered()), this, SLOT(OnSaveScreenshot()));

    Menu.exec(pView->mapToGlobal(_Pos));
  }

  void Mesh::OnFieldAction()
  {
    widgets::Mesh * pView = framework::GetPresenterView< widgets::Mesh * >(this);

    QAction *p_action = (QAction *) sender();
        
    mWrapper.mFieldName = p_action->text();
    on_update_model();
    pView->updateGL();
  }

  void Mesh::OnModelAction()
  {
    widgets::Mesh * pView = framework::GetPresenterView< widgets::Mesh * >(this);

    QAction *p_action = (QAction *) sender();

    disconnect(mWrapper.GetSubModel(), SIGNAL(changed()), this, SLOT(on_update_model()));

    mWrapper.SetSubModel(p_action->text());

    connect(mWrapper.GetSubModel(), SIGNAL(changed()), this, SLOT(on_update_model()));

    pView->RecalculateBounds();
    on_update_model();
    pView->updateGL();
  }

  void Mesh::OnElementPicked(IMesh::element_type _ElementType, int _ElementIndex)
  {
    CalculateStress(mWrapper.GetSubModel(), _ElementIndex);
  }

  void Mesh::OnElementPickedDouble(IMesh::element_type _ElementType, int _ElementIndex)
  {
    OnElementPicked(_ElementType, _ElementIndex);

    models::Mesh *pSubModel = mWrapper.GetSubModel()->findChild<models::Mesh *>();

    if (_ElementIndex >= 0 &&
        pSubModel)
    {
      const QString creator_id = "widget.mesh";

      widgets::Mesh *p_widget = GRef< framework::ComponentFactory >()->Make< widgets::Mesh >(creator_id);

      if (p_widget)
      {
        DockWidget * pDock = g_pMainFrame->make_dock(p_widget, "", creator_id, QUuid::createUuid());

        pDock->resize(640, 480);
        pDock->setFloating(true);
      }

      Mesh * pPresenter = framework::GetViewPresenter<Mesh *>(p_widget);

      pPresenter->mWrapper.mFieldName = mWrapper.mFieldName;
      pPresenter->mWrapper.mpSubModel = pSubModel;

      p_widget->RecalculateBounds();
      pPresenter->on_update_model();
    }
  }

  void Mesh::OnSaveScreenshot()
  {
    widgets::Mesh * pView = framework::GetPresenterView< widgets::Mesh * >(this);

    QDialog dlg;
    Ui::ScreenShot screenShotUI;
    screenShotUI.setupUi(&dlg);

    screenShotUI.width->setMinimum(1);
    screenShotUI.width->setMaximum(65535);
    screenShotUI.width->setValue(pView->width());
    screenShotUI.height->setMinimum(1);
    screenShotUI.height->setMaximum(65535);
    screenShotUI.height->setValue(pView->height());
    if (dlg.exec())
    {
      QString fileName = QFileDialog::getSaveFileName(0, tr("Save screenshot as"), QString(), "(*.png);;(*.bmp);;(*.jpg)");

      if (!fileName.isEmpty())
      {
        QPixmap pixmap = pView->renderPixmap(screenShotUI.width->value(), screenShotUI.height->value());

        if (!pixmap.save(fileName))
          QMessageBox::critical(0, tr("Save screenshot"), tr("Unable to save screenshot"));
      }
    }
  }

  void Mesh::UpdateModelLevelMap(QMultiMap<int, QObject *> & ModelLevelMap, int Level, QObject * pModel)
  {
    ModelLevelMap.insert(Level, pModel);

    foreach(QObject * pChildModel, pModel->children())
      UpdateModelLevelMap(ModelLevelMap, Level + 1, pChildModel);
  }

  //
  // presenters::models::Mesh::Palette
  //

  Palette::Palette(models::Mesh *p_model, widgets::Palette *p_view)
    : QObject(0)
  {
    const size_t field_index = p_model->get_current_scalar_field();
    if (field_index != size_t(-1))
    {
      p_view->blockSignals(true);
      connect(p_model, SIGNAL(changed()), this, SLOT(on_update_model()));
      connect(p_view, SIGNAL(stopsChanged(const QGradientStops &)), p_model, SLOT(set_gradient_stops(const QGradientStops &)));
      p_view->setStops(p_model->get_gradient_stops());
      { // value range
        models::Mesh::Scalar_field::SP p_field = p_model->get_frame()->get_param(field_index);
        p_view->setMinMax(p_field->get_min(), p_field->get_max());
      }
      p_view->blockSignals(false);
    }
  }

  Palette::~Palette()
  {

  }

  void Palette::on_update_model()
  {
    widgets::Palette * pView  = framework::GetPresenterView< widgets::Palette * >(this);
    models::Mesh     * pModel = framework::GetPresenterModel< models::Mesh * >(this);

    pView->blockSignals(true);
    pView->setStops(pModel->get_gradient_stops());
    { // value range
      const size_t field_index = pModel->get_current_scalar_field();
      models::Mesh::Scalar_field::SP p_field = pModel->get_frame()->get_param(field_index);
      pView->setMinMax(p_field->get_min(), p_field->get_max());
    }
    pView->blockSignals(false);
  }

  //
  // presenters::models::Mesh::Navigation
  //
  Navigation::Navigation(models::Mesh *p_model, widgets::Navigation *p_view)
    : QObject(0)
  {
    connect(p_model, SIGNAL(changed()), this, SLOT(on_model_changed()));
    connect(p_view, SIGNAL(frame_changed()), this, SLOT(on_frame_changed()));
    p_view->set_frame_qty(p_model->get_frame_qty());
  }

  Navigation::~Navigation()
  {
  }

  void Navigation::on_model_changed()
  {
    widgets::Navigation * pView  = framework::GetPresenterView< widgets::Navigation * >(this);
    models::Mesh        * pModel = framework::GetPresenterModel< models::Mesh * >(this);

    pView->set_current_frame(pModel->get_current_frame());
  }

  void Navigation::on_frame_changed()
  {
    widgets::Navigation * pView  = framework::GetPresenterView< widgets::Navigation * >(this);
    models::Mesh        * pModel = framework::GetPresenterModel< models::Mesh * >(this);

    pModel->set_current_frame(pView->get_current_frame());
  }

} } // namespace presenters { namespace models::Mesh {